#!/usr/bin/env bash

set -e

function show_usage {
    echo "Usage: $0 [options] <dirs>+"
    echo
    echo "Generate the sdbus++ sources from a directory path."
    echo
    echo "Options:"
    echo "    --tool <path>    - path to processing tool (default 'sdbus++')."
    echo "    --output <path>  - directory to place output files (default '.')."
    echo "    --list-all       - include all generated files in stdout list."
    echo "    --jobs <N>       - number to run in parallel (default: $(nproc))."
    echo "    <dirs>+          - any number of subdirectories to generate."
    echo
    echo "The output on stdout is a list of generated files, which is intended"
    echo "to be consumed by build systems, such as Meson."
    echo
    echo "This tool, by default, generates all files that are able to be"
    echo "created by sdbus++.  The output is a list of compilable sources that"
    echo "were generated by the tool.  The tool may generate outputs which are"
    echo "not able to be compiled, such as documentation, but it does not put"
    echo "them into stdout unless --list-all is given."
}

sdbuspp="sdbus++"
outputdir="."
listall="no"
parallel=$(nproc || grep -c ^processor < /proc/cpuinfo)

options="$(getopt -o ho:t:j: --long help,list-all,output:,tool:,jobs: -- "$@")"
eval set -- "$options"

while true; do
    case "$1" in
        -h | --help)
            show_usage
            exit
            ;;

        -j | --jobs)
            shift
            parallel="$1"
            shift
            ;;

        --list-all)
            listall="yes"
            shift
            ;;

        -o | --output)
            shift
            outputdir="$1"
            shift
            ;;

        -t | --tool)
            shift
            sdbuspp="$1"
            shift
            ;;

        --)
            shift
            break
            ;;
    esac
done

if [ $# -eq 0 ]; then
    show_usage
    exit 1
fi

all_jobs=""

declare -A emitted_file_names
# generate_single -- make a single call to sdbus++.
#   $1: sdbus++ TYPE
#   $2: sdbus++ PROCESS
#   $3: sdbus++ ITEM
#   $4: relative output file
#   $5: 'append-mode' if present.
function generate_single {

    if [ "empty" == "$1" ]; then
        echo -n > "$outputdir/$4"
    elif [ "" == "$5" ]; then
        $sdbuspp "$1" "$2" "$3" > "$outputdir/$4" &
        all_jobs="$all_jobs $!"
    else
        $sdbuspp "$1" "$2" "$3" >> "$outputdir/$4" &
        all_jobs="$all_jobs $!"
    fi

    # Emit filename as needed.
    filename=$outputdir/$4
    if [ "x1" != "x${emitted_file_names[$filename]}" ]; then
        emitted_file_names[$filename]="1"

        # Always emit generated file name for foo-cpp and foo-header.
        # Conditionally emit for everything else depending on $listall.
        case "$2" in
            *-cpp | *-header)
                echo "$filename"
                ;;

            *)
                if [ "yes" == "$listall" ]; then
                    echo "$filename"
                fi
                ;;
        esac
    fi

    # Ensure that no more than ${parallel} jobs are running at a time and if so
    # wait for them to finish.
    if [[ $(echo "$all_jobs" | wc -w) -ge $parallel ]]; then
        waitall
    fi
}

function waitall {
    for job in $all_jobs; do
        wait "$job"
    done
    all_jobs=""
}

for d in "$@"; do
    interfaces="$(find "$d" -name '*.interface.yaml')"
    errors="$(find "$d" -name '*.errors.yaml')"

    # Some files are created from multiple YAML sources, but we don't know
    # which YAML sources are present.  For these files, we need to first
    # create an empty file to ensure the file does not carry old data from
    # a previous run.
    for y in $interfaces $errors; do
        path="${y%.interface.yaml}"
        path="${path%.errors.yaml}"
        iface="${path//\//.}"

        mkdir -p "$outputdir/$path"
        generate_single empty markdown "$iface" "$path.md"
    done

    for i in $interfaces; do
        path="${i%.interface.yaml}"
        iface="${path//\//.}"

        generate_single interface server-header "$iface" "$path/server.hpp"
        generate_single interface server-cpp "$iface" "$path/server.cpp"
        generate_single interface client-header "$iface" "$path/client.hpp"
        generate_single interface markdown "$iface" "$path.md" append

    done
    waitall # finish all before continuing

    for e in $errors; do
        path="${e%.errors.yaml}"
        iface="${path//\//.}"

        generate_single error exception-header "$iface" "$path/error.hpp"
        generate_single error exception-cpp "$iface" "$path/error.cpp"
        generate_single error markdown "$iface" "$path.md" append

    done
    waitall # finish all before continuing
done
